package com.duoec.dw.lineage.service.impl;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.duoec.dw.lineage.core.conf.MySqlAnalyserConfig;
import com.duoec.dw.lineage.core.utils.AnalyserUtils;
import com.duoec.dw.lineage.dto.DdlChangeDto;
import com.duoec.dw.lineage.dto.TableFieldDto;
import com.duoec.dw.lineage.dto.TableInfoDto;
import com.duoec.dw.lineage.enums.DatabaseTypeEnum;
import com.duoec.dw.lineage.exceptions.LineageAnalyseException;
import com.duoec.dw.lineage.service.impl.analyser.statement.MySqlUseStatementAnalyser;
import com.duoec.dw.lineage.service.MySqlAnalyser;
import com.duoec.dw.lineage.service.StatementAnalyser;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.LinkedHashSet;
import java.util.List;

/**
 * @author xuwenzhen
 */
@Service
public class MySqlAnalyserImpl implements MySqlAnalyser {
    private static final Logger logger = LoggerFactory.getLogger(MySqlAnalyserImpl.class);
    private static final String FIELD_NAME_QUOTE_1 = "`";
    private static final String FIELD_NAME_QUOTE_2 = "'";
    private static final String TURN_LINE = "\n";

    @Autowired
    private MySqlAnalyserConfig mySqlAnalyserConfig;

    /**
     * 通过schema获取表结构信息
     *
     * @param sql 脚本
     * @return 表结构
     */
    @Override
    public TableInfoDto schemaAnalyse(String sql) {
        SQLStatement statement = SQLUtils.parseSingleMysqlStatement(sql);
        if (!(statement instanceof MySqlCreateTableStatement)) {
            throw new LineageAnalyseException("非创建表脚本：" + sql);
        }
        TableInfoDto table = new TableInfoDto();
        table.setDatabaseType(DatabaseTypeEnum.MYSQL.getValue());
        table.setComment(getTableComment((MySqlCreateTableStatement) statement));
        table.setTableName(getTableName((MySqlCreateTableStatement) statement));
        MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) statement;
        List<SQLTableElement> fieldElements = createTableStatement.getTableElementList();
        LinkedHashSet<TableFieldDto> fields = new LinkedHashSet<>();
        table.setFields(fields);
        fieldElements.forEach(fieldElement -> {
            if (fieldElement instanceof SQLColumnDefinition) {
                TableFieldDto field = getField((SQLColumnDefinition) fieldElement);
                field.setIndex(fields.size());
                fields.add(field);
            }
        });
        return table;
    }

    /**
     * 解析DDL变更脚本
     *
     * @param sql 变更DDL脚本，可能会有很多段！
     * @return 变更影响范围
     */
    @Override
    public List<DdlChangeDto> ddlChangeAnalyse(String sql) {
        MySqlStatementParser parser = new MySqlStatementParser(sql);
        List<SQLStatement> statementList = parser.parseStatementList();
        if (CollectionUtils.isEmpty(statementList)) {
            return Lists.newArrayList();
        }

        List<DdlChangeDto> list = Lists.newArrayList();
        DdlChangeDto dto = null;
        for (SQLStatement statement : statementList) {
            StatementAnalyser analyser = mySqlAnalyserConfig.getStatementAnalyser(statement);
            if (analyser == null) {
                logger.warn("未实现类[{}]的解析器！sql={}", statement.getClass().getName(), sql);
                continue;
            }
            Object result = analyser.analyse(statement);
            if (analyser instanceof MySqlUseStatementAnalyser) {
                //切换库，重新创建一个实例
                dto = initDdlChangeDto();
                dto.setSql(statement.toString());
                list.add(dto);
                dto.setDatabaseName((String) result);
                continue;
            }
            if (dto == null) {
                if (result instanceof DdlChangeDto) {
                    dto = (DdlChangeDto) result;
                    list.add(dto);
                } else {
                    logger.warn("脚本未指定database!sql={}", sql);
                    continue;
                }
            }

            dto.setSql(dto.getSql() + TURN_LINE + statement.toString());
            analyser.setResult(dto, result);
        }

        return list;
    }

    @NotNull
    private DdlChangeDto initDdlChangeDto() {
        DdlChangeDto dto = new DdlChangeDto();
        dto.setItems(Lists.newArrayList());
        return dto;
    }


    private String getTableComment(MySqlCreateTableStatement statement) {
        SQLExpr comment = statement.getComment();
        if (comment != null) {
            return AnalyserUtils.formatFieldName(comment.toString());
        }
        return null;
    }

    private TableFieldDto getField(SQLColumnDefinition fieldElement) {
        TableFieldDto field = new TableFieldDto();
        String name = fieldElement.getName().getSimpleName();
        SQLExpr comment = fieldElement.getComment();
        if (comment != null) {
            field.setComment(comment.toString());
        }
        field.setDataType(fieldElement.getDataType().toString());
        field.setColName(AnalyserUtils.formatFieldName(name));
        return field;
    }

    private String getTableName(MySqlCreateTableStatement statement) {
        SQLExprTableSource tableSource = statement.getTableSource();
        SQLExpr expr = tableSource.getExpr();
        if (expr instanceof SQLIdentifierExpr) {
            String tableName = ((SQLIdentifierExpr) expr).getName();
            if (tableName.startsWith(FIELD_NAME_QUOTE_1) || tableName.startsWith(FIELD_NAME_QUOTE_2)) {
                tableName = tableName.substring(1, tableName.length() - 1);
            }
            return tableName;
        }
        return null;
    }

    public void setMySqlAnalyserConfig(MySqlAnalyserConfig mySqlAnalyserConfig) {
        this.mySqlAnalyserConfig = mySqlAnalyserConfig;
    }
}
